#ifndef AVALON_SERVERMIXIN_HPP
#define AVALON_SERVERMIXIN_HPP
#include <boost/call_traits.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include "Avalon/ServiceLocator/DirectoryEntry.hpp"
#include "Avalon/ServiceLocator/ServiceLocatorClient.hpp"
#include "Avalon/Services/Services.hpp"
#include "Avalon/Threading/LockRelease.hpp"

namespace Avalon {
namespace Services {

  /*! \class ServerMixin
   *  \brief Contains common functionality used by servers providing an Avalon
   *         service.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   *  \tparam ChannelType The Channel sub-type used to represent clients.
   */
  template<typename ChannelType>
  class ServerMixin {
    public:

      //! Constructs a ServerMixin.
      ServerMixin();

      virtual ~ServerMixin();

      //! Sets the request handler for a service by queueing it.
      /*!
        \tparam Service The Service being handled.
        \tparam SignalHandlerType The type of signal handler to enqueue the
                operation to.
        \tparam MutexType The synchronization primitive to use.
        \param serviceNode The ServiceNode receiving the Service request.
        \param signalHandler The signal handler to enqueue the operation to.
        \param mutex The mutex to lock when enqueuing the operation.
        \param slot The operation's handler.
      */
      template<typename Service, typename SignalHandlerType, typename MutexType>
      void SetHandler(ServiceNode& serviceNode,
        SignalHandlerType& signalHandler, MutexType& mutex, const
        boost::function<typename Service::Request::Signal::signature_type>&
        slot);

      //! Marks an operation as pending, queuing it for later processing.
      /*!
        \tparam SignalHandlerType The type of signal handler to enqueue the
                operation to.
        \tparam MutexType The synchronization primitive to use.
        \param baseChannel The Channel of the client that made the request.
        \param signalHandler The signal handler to enqueue the operation to.
        \param mutex The mutex to lock when enqueuing the operation.
        \param operation The operation to enqueue.
      */
      template<typename SignalHandlerType, typename MutexType>
      void AddPendingOperation(IO::Channel* baseChannel,
        SignalHandlerType& signalHandler, MutexType& mutex,
        const boost::function<void ()>& operation);

      //! Authenticates a session.
      /*!
        \tparam MutexType The synchronization primitive to use.
        \param mutex The mutex to lock when enqueuing the operation.
        \param channel The client Channel to authenticate.
        \param serviceNode The ServiceNode receiving the Service request.
        \param serviceClient The ServiceClient that will perform the
                             authentication.
      */
      template<typename MutexType>
      void AuthenticateSession(MutexType& mutex, ChannelType* channel,
        ServiceNode& serviceNode,
        ServiceLocator::ServiceLocatorClient* serviceClient);

      //! Removes a client Channel, ending it's session.
      /*!
        \param client The client Channel to remove.
      */
      virtual void RemoveClient(ChannelType* client) = 0;

    protected:

      //! Handles a client Channel that successfully authenticated.
      /*!
        \param channel The client Channel that was accepted.
      */
      virtual void HandleSessionAccepted(ChannelType* channel);

      //! Handles a client Channel whose authentication was rejected.
      /*!
        \param channel The client Channel that was rejected.
      */
      virtual void HandleSessionRejected(ChannelType* channel);

    private:
      template<typename SignalHandlerType, typename MutexType>
      void OnChannelOperationComplete(ChannelType* channel,
        SignalHandlerType& signalHandler, MutexType& mutex,
        const Avalon::Threading::Async<void>::Ptr& result);

      template<typename Service, int Arity =
        Service::Request::Signal::arity - 1>
      struct GetWrapperSlot {};

      #ifndef AVALON_DECLARE_SERVERMIXIN_CALL_ARGUMENTS
        #define AVALON_DECLARE_SERVERMIXIN_CALL_ARGUMENTS 10
      #endif

      #define AVALON_DECLARE_SERVERMIXIN_DEFINE_PARAMETER(z, n, q)             \
        BOOST_PP_COMMA_IF(n) typename boost::call_traits<                      \
          typename Service::Parameter##n::Type>::param_type a##n

      #define BOOST_PP_LOCAL_MACRO(n)                                          \
      template<typename Service>                                               \
      struct GetWrapperSlot<Service, n> {                                      \
        template<typename SignalHandlerType, typename MutexType>               \
        static typename Service::Request::Signal::slot_type Invoke(            \
          ServerMixin* server, SignalHandlerType& signalHandler,               \
          MutexType& mutex, const boost::function<                             \
          typename Service::Request::Signal::signature_type>& slot);           \
      };                                                                       \
                                                                               \
      template<typename Service, typename SignalHandlerType,                   \
        typename MutexType>                                                    \
      void Slot##n(const Services::RequestToken<Service>& request,             \
        SignalHandlerType& signalHandler, MutexType& mutex, const              \
        boost::function<typename Service::Request::Signal::signature_type>&    \
        slot BOOST_PP_COMMA_IF(n)                                              \
        BOOST_PP_REPEAT(n, AVALON_DECLARE_SERVERMIXIN_DEFINE_PARAMETER, A));

      #define BOOST_PP_LOCAL_LIMITS                                            \
        (0, AVALON_DECLARE_SERVERMIXIN_CALL_ARGUMENTS)
      #include BOOST_PP_LOCAL_ITERATE()
  };

  template<typename ChannelType>
  ServerMixin<ChannelType>::ServerMixin() {}

  template<typename ChannelType>
  ServerMixin<ChannelType>::~ServerMixin() {}

  template<typename ChannelType>
  template<typename Service, typename SignalHandlerType, typename MutexType>
  void ServerMixin<ChannelType>::SetHandler(ServiceNode& serviceNode,
      SignalHandlerType& signalHandler, MutexType& mutex,
      const boost::function<typename Service::Request::Signal::signature_type>&
      slot) {
    typename Service::Request::Signal::slot_type wrapperSlot =
      GetWrapperSlot<Service>::template Invoke<SignalHandlerType, MutexType>(
      this, signalHandler, mutex, slot);
    SetRequestHandler<Service>(serviceNode, signalHandler, wrapperSlot);
  }

  template<typename ChannelType>
  template<typename SignalHandlerType, typename MutexType>
  void ServerMixin<ChannelType>::AddPendingOperation(IO::Channel* baseChannel,
      SignalHandlerType& signalHandler, MutexType& mutex,
      const boost::function<void ()>& operation) {
    ChannelType* channel = static_cast<ChannelType*>(baseChannel);
    channel->GetTaskQueue().AddTask(operation);
    if(channel->GetTaskQueue().GetRunningTaskCount() == 0) {
      Threading::Async<void>::Ptr result =
        channel->GetTaskQueue().StartNextTask();
      result->ConnectFinishedSignal(
        signalHandler.template GetSlot<Threading::BaseAsync::FinishedSignal>(
        boost::bind(&ServerMixin::OnChannelOperationComplete<SignalHandlerType,
        MutexType>, this, channel, boost::ref(signalHandler), boost::ref(mutex),
        result)));
    }
  }

  template<typename ChannelType>
  template<typename MutexType>
  void ServerMixin<ChannelType>::AuthenticateSession(MutexType& mutex,
      ChannelType* channel, ServiceNode& serviceNode,
      ServiceLocator::ServiceLocatorClient* serviceClient) {
    boost::unique_lock<MutexType> lock(mutex);
    try {
      unsigned int key = ServiceLocator::GenerateEncryptionKey();
      ServiceLocator::GetSessionIdService::ResponsePtr sessionIdResponse =
        serviceNode.SendRequest<ServiceLocator::GetSessionIdService>(channel,
        key);
      sessionIdResponse->Wait(lock);
      ServiceLocator::GetSessionIdService::ReturnType sessionIdResult =
        sessionIdResponse->Get();
      if(!sessionIdResult.get<0>()) {
        serviceNode.SendMessage(channel,
          ServiceLocator::SessionAuthenticationMessage(false));
        channel->GetConnection().Close();
        return;
      }
      const std::string& sessionId = sessionIdResult.get<2>();
      Threading::LockRelease<boost::unique_lock<MutexType> > release(lock);
      ServiceLocator::DirectoryEntry::Tag account =
        serviceClient->AuthenticateSession(sessionId, key);
      channel->SetAccount(account);
    } catch(std::exception&) {
      serviceNode.SendMessage(channel,
        ServiceLocator::SessionAuthenticationMessage(false));
      HandleSessionRejected(channel);
      channel->GetConnection().Close();
      return;
    }
    serviceNode.SendMessage(channel,
      ServiceLocator::SessionAuthenticationMessage(true));
    HandleSessionAccepted(channel);
  }

  template<typename ChannelType>
  void ServerMixin<ChannelType>::HandleSessionAccepted(ChannelType* channel) {}

  template<typename ChannelType>
  void ServerMixin<ChannelType>::HandleSessionRejected(ChannelType* channel) {}

  template<typename ChannelType>
  template<typename SignalHandlerType, typename MutexType>
  void ServerMixin<ChannelType>::OnChannelOperationComplete(
      ChannelType* channel, SignalHandlerType& signalHandler, MutexType& mutex,
      const Threading::Async<void>::Ptr& result) {
    boost::lock_guard<MutexType> lock(mutex);
    channel->GetTaskQueue().MarkTaskAsFinished(result);
    if(channel->GetTaskQueue().GetRunningTaskCount() == 0) {
      if(channel->GetTaskQueue().GetPendingTaskCount() != 0) {
        Threading::Async<void>::Ptr result =
          channel->GetTaskQueue().StartNextTask();
        result->ConnectFinishedSignal(
          signalHandler.template GetSlot<Threading::BaseAsync::FinishedSignal>(
          boost::bind(&ServerMixin::OnChannelOperationComplete<
            SignalHandlerType, MutexType>, this, channel,
            boost::ref(signalHandler), boost::ref(mutex), result)));
      } else if(!channel->GetConnectionState().IsOpen()) {
        RemoveClient(channel);
      }
    }
  }

  #define AVALON_DEFINE_SERVERMIXIN_DEFINE_PARAMETER(z, n, q)                  \
    BOOST_PP_COMMA_IF(n) typename boost::call_traits<                          \
      typename Service::Parameter##n::Type>::param_type a##n

  #define AVALON_DEFINE_SERVERMIXIN_PLACEHOLDERS(z, n, a)                      \
    BOOST_PP_COMMA_IF(n) boost::arg<n + 2>()

  #define AVALON_DEFINE_SERVERMIXIN_SLOT_CALL_COMMA_COPY(z, n, a)              \
    BOOST_PP_COMMA_IF(n) a##n

  #define BOOST_PP_LOCAL_MACRO(n)                                              \
  template<typename ChannelType>                                               \
  template<typename Service>                                                   \
  template<typename SignalHandlerType, typename MutexType>                     \
  typename Service::Request::Signal::slot_type ServerMixin<ChannelType>::      \
      GetWrapperSlot<Service, n>::Invoke(ServerMixin* server,                  \
      SignalHandlerType& signalHandler, MutexType& mutex,                      \
      const boost::function<typename Service::Request::Signal::signature_type>&\
      slot) {                                                                  \
    return boost::bind(&ServerMixin::Slot##n<Service, SignalHandlerType,       \
      MutexType>, server, boost::arg<1>(), boost::ref(signalHandler),          \
      boost::ref(mutex), slot BOOST_PP_COMMA_IF(n) BOOST_PP_REPEAT(n,          \
      AVALON_DEFINE_SERVERMIXIN_PLACEHOLDERS, a));                             \
  }                                                                            \
                                                                               \
  template<typename ChannelType>                                               \
  template<typename Service, typename SignalHandlerType, typename MutexType>   \
  void ServerMixin<ChannelType>::Slot##n(                                      \
      const Services::RequestToken<Service>& request,                          \
      SignalHandlerType& signalHandler, MutexType& mutex,                      \
      const boost::function<typename Service::Request::Signal::signature_type>&\
      slot BOOST_PP_COMMA_IF(n) BOOST_PP_REPEAT(n,                             \
      AVALON_DEFINE_SERVERMIXIN_DEFINE_PARAMETER, A)) {                        \
    boost::lock_guard<MutexType> lock(mutex);                                  \
    AddPendingOperation(request.GetChannel(), signalHandler, mutex,            \
      boost::bind(slot, request BOOST_PP_COMMA_IF(n) BOOST_PP_REPEAT(n,        \
      AVALON_DEFINE_SERVERMIXIN_SLOT_CALL_COMMA_COPY, a)));                    \
  }

  #define BOOST_PP_LOCAL_LIMITS                                                \
    (0, AVALON_DECLARE_SERVERMIXIN_CALL_ARGUMENTS)
  #include BOOST_PP_LOCAL_ITERATE()
}
}

#endif // AVALON_SERVERMIXIN_HPP
